#include "GLWidget.h"

GLWidget::GLWidget(QWidget *parent)
    : QGLWidget(QGLFormat(QGL::SampleBuffers), parent),
      mpEngine(0),
      mpViewport(0),
      mpCamera(0),
      mpLight(0),
      mpRender(0),
      mNumQuad(1)
{
    makeCurrent();

    mpEngine = ne::Engine::GetSingletonPtr();
    if (!mpEngine)
    {
        mpEngine = new ne::Engine();
    }

    mpCamera = new ne::Camera();
    mCameraLookAt = ne::Vector3::ZERO;
    mCameraDistance = 100.0f;
    mCameraYaw = 0.0f;
    mCameraPitch = 0.0f;
    formatCamera();

    mpViewport = new ne::Viewport(mpCamera);

    mpLight = new ne::Light();
    mpLight->setType(ne::LT_POINT);
    mpLight->setAmbient(ne::Color::GRAY);
    mpLight->setAttenuationFactor(ne::LAT_LINEAR, 0.005f);
    mpLight->setPosition(ne::Vector3(0.0f, 0.0f, 1000.0f));

    mpRender = new EditorRender();
    mpRender->setLight(mpLight);

    setMinimumSize(QSize(400, 300));
    setMouseTracking(true);
    setFocusPolicy(Qt::WheelFocus);

    startTimer(20);
}

GLWidget::~GLWidget()
{
}

void GLWidget::formatCamera()
{
    mCameraYaw = std::fmod(mCameraYaw, 360.0f);
    ne::Math::RealClamp(mCameraPitch, -90.0f, 90.0f);
    ne::Quaternion ori;
    ne::Quaternion q(ne::Vector3::UNIT_X, ne::Math::PI * mCameraPitch / 180.0f);
    ori = q * ori;
    q.fromAxisAngle(ne::Vector3::UNIT_Y, ne::Math::PI * mCameraYaw / 180.0f);
    ori = q * ori;
    mpCamera->setOrientation(ori);

    if (mCameraDistance < 0.0f)
    {
        mCameraDistance = 0.0f;
    }
    mpCamera->setPosition(mCameraLookAt + mCameraDistance * mpCamera->getBack());
}

void GLWidget::createQuad()
{
    ++mNumQuad;
}

void GLWidget::deleteQuad()
{
    if (mNumQuad > 1)
    {
        --mNumQuad;
    }
}

void GLWidget::initializeGL()
{
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClearDepth(1);

    glDepthFunc(GL_LEQUAL);
    glEnable(GL_DEPTH_TEST);

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glCullFace(GL_BACK);
    //glEnable(GL_CULL_FACE);

    glDisable(GL_LIGHTING);
    glDisable(GL_COLOR_SUM);
}

void GLWidget::paintGL()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    const ne::Matrix4 &viewMatrix = mpCamera->getViewMatrix();
    mpEngine->getRenderSystem()->setViewMatrix(viewMatrix);

    glPushMatrix();

    glColor4f(1.0f, 0.5f, 0.0f, 1.0f);
    glBegin(GL_QUADS);
    for (int i=0; i<mNumQuad; ++i)
    {
        ne::Vector3 pos = mQuadPos + ne::Vector3(3.0f * i, 0.0f, 0.0f);
        glVertex3f(pos.x - 1.0f, pos.y - 1.0f, pos.z);
        glVertex3f(pos.x + 1.0f, pos.y - 1.0f, pos.z);
        glVertex3f(pos.x + 1.0f, pos.y + 1.0f, pos.z);
        glVertex3f(pos.x - 1.0f, pos.y + 1.0f, pos.z);
    }
    glEnd();

    mpRender->render(mpViewport);

    glPopMatrix();
}

void GLWidget::resizeGL(int width, int height)
{
    glViewport(0, 0, width, height);

    mpViewport->setPosition(0, 0);
    mpViewport->resize(width, height);

    glMatrixMode(GL_PROJECTION);
    const ne::Matrix4 &projectionMatrix = mpCamera->getProjectionMatrix();
    glLoadMatrixf(projectionMatrix.ptr());
    glMatrixMode(GL_MODELVIEW);
}

void GLWidget::mouseMoveEvent(QMouseEvent *event)
{
    ne::Ray outRay;
    mpViewport->getScreenToWorldRay(ne::real(event->x()), ne::real(event->y()), outRay);
    ne::Plane p(ne::Vector3::UNIT_Z, 0.0f);
    ne::Vector3 point = p.projectVector(outRay.origin);
    ne::Vector3 d = point - outRay.origin;
    ne::real u = d.dotProduct(outRay.direction);
    mQuadPos = outRay.origin + (d.squaredLength() / u) * outRay.direction;

    if (mpEngine->getKeyboard()->isModifierDown(ne::KM_ALT))
    {
        ne::real mousePosX, mousePosY;
        mpEngine->getMouse()->getPosition(mousePosX, mousePosY);
        ne::real deltaX = 0.1f * (event->x() - mousePosX);
        ne::real deltaY = 0.1f * (event->y() - mousePosY);
        if (mpEngine->getMouse()->isButtonDown(ne::MB_LEFT_BUTTON))
        {
            mCameraYaw -= deltaX;
            mCameraPitch -= deltaY;
            formatCamera();
        }
        else if (mpEngine->getMouse()->isButtonDown(ne::MB_MIDDLE_BUTTON))
        {
            mCameraLookAt += deltaX * mpCamera->getLeft();
            mCameraLookAt += deltaY * mpCamera->getUp();
            formatCamera();
        }
        else if (mpEngine->getMouse()->isButtonDown(ne::MB_RIGHT_BUTTON))
        {
            mCameraLookAt += deltaY * mpCamera->getBack();
            formatCamera();
        }
    }
    mpEngine->setMousePosition(ne::real(event->x()), ne::real(event->y()));
}

void GLWidget::mousePressEvent(QMouseEvent *event)
{
    mpEngine->setMouseButtonDown(ConvertQTMouseButton(event->button()));
}

void GLWidget::mouseReleaseEvent(QMouseEvent *event)
{
    mpEngine->setMouseButtonUp(ConvertQTMouseButton(event->button()));
}

void GLWidget::mouseDoubleClickEvent(QMouseEvent *event)
{
}

void GLWidget::wheelEvent(QWheelEvent *event)
{
    ne::real delta = event->delta();
    mCameraDistance -= 0.1f * delta;
    formatCamera();
    mpEngine->setMouseWheelVariation(delta);
}

void GLWidget::keyPressEvent(QKeyEvent *event)
{
    mpEngine->setKeyboardKeyDown(ne::Keyboard::ConvertASCIIKey(event->key()));

    Qt::KeyboardModifiers modifiers = event->modifiers();
    if (modifiers & Qt::ShiftModifier)
    {
        mpEngine->setKeyboardModifierDown(ConvertQTKeyboardModifier(Qt::ShiftModifier));
    }
    if (modifiers & Qt::ControlModifier)
    {
        mpEngine->setKeyboardModifierDown(ConvertQTKeyboardModifier(Qt::ControlModifier));
    }
    if (modifiers & Qt::AltModifier)
    {
        mpEngine->setKeyboardModifierDown(ConvertQTKeyboardModifier(Qt::AltModifier));
    }
}

void GLWidget::keyReleaseEvent(QKeyEvent *event)
{
    mpEngine->setKeyboardKeyUp(ne::Keyboard::ConvertASCIIKey(event->key()));

    Qt::KeyboardModifiers modifiers = event->modifiers();
    if ((modifiers & Qt::ShiftModifier) == 0)
    {
        mpEngine->setKeyboardModifierUp(ConvertQTKeyboardModifier(Qt::ShiftModifier));
    }
    if ((modifiers & Qt::ControlModifier) == 0)
    {
        mpEngine->setKeyboardModifierUp(ConvertQTKeyboardModifier(Qt::ControlModifier));
    }
    if ((modifiers & Qt::AltModifier) == 0)
    {
        mpEngine->setKeyboardModifierUp(ConvertQTKeyboardModifier(Qt::AltModifier));
    }
}

ne::MouseButton GLWidget::ConvertQTMouseButton(const Qt::MouseButton button)
{
    switch (button)
    {
        case Qt::NoButton: return ne::MB_NONE_BUTTON;
        case Qt::LeftButton: return ne::MB_LEFT_BUTTON;
        case Qt::RightButton: return ne::MB_RIGHT_BUTTON;
        case Qt::MiddleButton: return ne::MB_MIDDLE_BUTTON;
        case Qt::XButton1: return ne::MB_BUTTON3;
        case Qt::XButton2: return ne::MB_BUTTON4;
        default: return ne::MB_NONE_BUTTON;
    }
}

ne::KeyboardModifier GLWidget::ConvertQTKeyboardModifier(const Qt::KeyboardModifier modifier)
{
    switch (modifier)
    {
        case Qt::NoButton: return ne::KM_NONE;
        case Qt::ShiftModifier: return ne::KM_SHIFT;
        case Qt::ControlModifier: return ne::KM_CTRL;
        case Qt::AltModifier: return ne::KM_ALT;
        default: return ne::KM_NONE;
    }
}
